/*
 * date:    2017.04.24
 * version: v1.0.3.0
 * author:  blqw
 */
(function (window, name) {
  if (name in window) {
    throw new Error(["already '", name, "' in 'window'"].join(''))
  }

  function trim (str) {
    switch (typeof str) {
      case 'undefined': return ''
      case 'boolean': return str ? 'true' : ''
      case 'string': return str.replace(/^\s+|\s+$/g, '')
      case 'number': return str.toString()
      case 'object': return trim(str || '')
      case 'function': return trim(str())
    }
  }

  // 在字符串中查找指定的字符,如果不存在返回字符串长度
  function findIndex (str, chars) {
    let r = str.length
    for (let i = 0; i < chars.length; i++) {
      let j = str.indexOf(chars[i])
      if (j >= 0 && r > j) { r = j }
    }
    return r
  }

  // 处理url中的 \..
  function backtrack (path) {
    while (path !== (path = path.replace(/((\/|^)[^\/]+\/\.\.)|^\/\.\.|^\.\./g, ''))) {
    }
    return path
  }

  function parseSearch (search) {
    if (/^[^#][^?#]*[?#]/.test(search)) {
      throw new Error('Argument format is invalid. from string : "' + search + '"')
    }
    search = trim(search)
    if (search.charAt(0) === '?') {
      search = search.substr(1)
    }
    let params = {}
    if (search.charAt(0) === '&') {
      params[''] = ''
      search = search.substr(1)
    }
    if (search === '') return params
    let args = search.match(/[?]?[^=&]+=[^=&]+/g)
    if (args == null) return params
    for (let j = 0; j < args.length; j++) {
      try {
        let kv = args[j].split('=')
        setPropValue(params, decodeURIComponent(kv[0]), decodeURIComponent(kv[1]))
      } catch (e) {
        console.debug(e)
      }
    }
    return params
    // ---- function ----

    // 设置属性的值
    function setPropValue (object, name, value) {
      let reg = /\[([0-9]*)\]|\[([^\[\]\.]+)\]|\.([^\[\]\.]+)|^([^\[\]\.]+)|\./g
      let p = {
        get: function () { return object },
        set: function (value) { object[''] = (object[''] === undefined ? value : [object[''], value]) },
        build: function () { return object },
        append: function (name) { return new prop(p, name) }
      }
      let m = null
      while (m = reg.exec(name)) {
        if (m[1] != null) {
          p = p.append(parseInt(m[1]) || 0)
        } else {
          let n = m[2] || m[3] || m[4]
          if (n == null) {
            object[name] = value
            return object
          }
          p = p.append(n)
        }
      }
      p.set(value)
      return p.build()
    }

    function prop (ref, name) {
      this.ref = ref
      this.name = name
      this.build = this.ref.build
      this.get = function () {
        let object = this.ref.get()
        if (object == null) {
          return null
        }
        return object[this.name]
      }
      this.set = function (value) {
        let object = this.ref.get()
        if (object == null) {
          if (typeof this.name === 'number') {
            this.ref.set(object = [])
          } else {
            this.ref.set(object = {})
          }
        }
        if (object[name] !== undefined) {
          object[name] = [object[name], value]
        } else {
          object[name] = value
        }
      }
      this.append = function (name) {
        return this.next = new prop(this, name)
      }
    }
  }

  function urlencoded (params) {
    if (params == null || Object.keys(params).length === 0) {
      return ''
    }
    let arr = [];
    (function f (value, name) {
      switch (typeof value) {
        case 'undefined':
          break
        case 'boolean':
          arr.push(name + '=' + value)
          break
        case 'string':
          if (name == null || name === '') {
            arr.push(encodeURIComponent(value))
          } else {
            arr.push(name + '=' + encodeURIComponent(value))
          }
          break
        case 'number':
          arr.push(name + '=' + value)
          break
        case 'object':
          if (value instanceof Array) {
            for (let index = 0; index < value.length; index++) {
              f(value[index] || '', name.length > 0 ? name + '%5B' + (index || '') + '%5D' : '')
            }
            return
          }
          if (value !== null) {
            let keys = Object.keys(value)
            if (keys.length > 0) {
              for (let index = 0; index < keys.length; index++) {
                let key = encodeURIComponent(keys[index])
                if (key == null) {
                  key = ''
                }
                if (name.length !== 0) {
                  key = name + '%5B' + key + '%5D'
                }
                let val = value[keys[index]]
                if (val == null) {
                  val = ''
                }
                f(val, key)
              }
            } else if (value instanceof Date) {
              let date = value.getFullYear() +
                '-' + ('0' + (date.getMonth() + 1)).slice(-2) +
                '-' + ('0' + value.getDate()).slice(-2) +
                ' ' + ('0' + value.getHours()).slice(-2) +
                '%3A' + ('0' + value.getMinutes()).slice(-2) +
                '%3A' + ('0' + value.getSeconds()).slice(-2)
            } else {
              f(value.toString(), name)
            }
          }
          break
        case 'function':
          f(value(), name)
          break
      }
    })(params, '')
    return arr.join('&')
  }

  let token = new Object()

  function Url (url) {
    if (arguments[1] !== token) {
      return new Url(url, token)
    }
    url = trim(url || window.location.href)

    let _scheme = null // http://
    let _domain = null // baidu.com
    let _path = null // /a/b/c
    let _query = null // ?id=1
    let _anchor = null // #abc

    if (url.length > 0) {
      let scheme = /^([^:/]+:\/\/|\/\/)/.exec(url) || ''
      if (scheme && scheme.length > 0) {
        _scheme = scheme[0]
        url = url.substr(_scheme.length)
        _domain = url.substr(0, findIndex(url, ['/', '\\', '?', '#'])).replace(/[\/\\]$/g, '')
        if (_domain.indexOf('.') === -1) {
          //throw new Error('The "url" argument is invalid. because "domian" doesn\'t exist. from string : "' + arguments[0] + '"')
        }
        url = url.substr(_domain.length)
      }
      _path = url.substr(0, findIndex(url, ['?', '#'])).replace(/[?#]$/g, '')
      url = url.substr(_path.length)
      _path = _path.replace(/(\\|\/)+/g, '/')
      _query = url.substr(0, findIndex(url, ['#']))
      url = url.substr(_query.length)
      _anchor = url
    }

    this.params = parseSearch(_query)
    let me = this

    if (typeof Object.defineProperties === 'function') {
      let error = function (name, value) {
        throw new Error('The "' + name + '" format is invalid. from string : "' + value + '"')
      }
      Object.defineProperties(this, {
        scheme: {
          get: function () { return _scheme },
          set: function (value) {
            value = trim(value)
            if (/^([a-z]+:)?\/\/$/.test(value) === false) {
              error('scheme', value)
            }
            _scheme = value
          }
        },
        domain: {
          get: function () { return _domain },
          set: function (value) {
            value = trim(value)
            if (/^([a-z0-9]([a-z0-9\-]+[a-z0-9])?\.)+[a-z0-9]+\/?$/.test(value) === false) {
              error('domain', value)
            }
            if (value.slice(-1) === '/') {
              value = value.slice(0, -1)
            }
            _domain = value
          }
        },
        path: {
          get: function () {
            let path = _path
            if (_scheme == null || _scheme === '' || _domain == null || _domain !== '') {
              return path
            }
            path = backtrack(path)
            if (path.charAt(0) !== '/') {
              path = '/' + path
            }
            return path
          },
          set: function (value) {
            value = trim(value).replace(/([^\/]|^)[\/]{2,}/g, function (m) { return m.charAt(0) === ':' ? '://' : m.charAt(0) + '/' })
            if (/^\/?(([^\/?#]+)(\/|$))+$/.test(value) === false) {
              error('path', value)
            }
            _path = value
          }
        },
        query: {
          get: function () {
            let str = urlencoded(me.params)
            return (str === '') ? '' : '?' + str
          },
          set: function (value) {
            me.params = parseSearch(value)
          }
        },
        anchor: {
          get: function () { return (_anchor === '' || _anchor.charAt(0) === '#') ? _anchor : '#' + _anchor },
          set: function (value) { _anchor = trim(value) }
        }
      })
    } else {
      this.scheme = _scheme
      this.domain = _domain
      this.path = _path
      this.query = _query
      this.anchor = _anchor
    }

    this.toString = function () {
      let path = this.path
      if (_scheme == null || _scheme === '' || _domain == null || _domain !== '') {
        path = backtrack(path)
        if (path.charAt(0) !== '/') {
          path = '/' + path
        }
      }
      return [this.scheme, this.domain, path, this.query, this.anchor === '#' ? '' : this.anchor].join('')
    }
  }

  function combine (url1, url2) {
    if (url1 == null || url1.length === 0) {
      return url2 && new Url(url2).toString()
    }
    if (url2 == null || url2.length === 0) {
      return new Url(url1).toString()
    }
    if (/^\.\.[^/]/.test(url2)) {
      url2 = './' + url2
    }
    let u1 = new Url(url1)
    let u2 = new Url(url2)

    if (u2.scheme != null) {
      if (u2.scheme === '//' && u1.scheme != null) {
        u2.scheme = u1.scheme
      }
      return u2.toString()
    }

    if (u2.path != null && u2.path.length > 0) {
      if (u2.path.charAt(0) === '/') {
        u1.path = u2.path
      } else if (u2.path.substr(0, 2) === './') {
        u1.path = [u1.path, u2.path.substr(u1.path.slice(-1) === '/' ? 2 : 1)].join('')
      } else if (u1.path.slice(-1) === '/') {
        u1.path = [u1.path, u2.path].join('')
      } else if (u2.path.charAt(0) !== '.') {
        u1.path = [u1.path.substr(0, u1.path.lastIndexOf('/')), '/', u2.path].join('')
      }
    }

    if (u2.query == null || u2.query.length === 0) {
      if (u2.anchor != null && u2.anchor.length > 0) {
        u1.anchor = u2.anchor
      } else {
        u1.query = ''
      }
      return u1.toString()
    }

    if (u2.query.substr(0, 2) === '?&') {
      if (u2.query.length > 2) {
        if (u1.query == null || u1.query.length === 0) {
          u1.query = ['?', u2.query.substr(2)]
        } else {
          u1.query = [u1.query, u2.query.substr(u1.query.slice(-1) === '&' ? 2 : 1)].join('')
        }
      }
    } else {
      u1.query = u2.query
    }

    if (u2.anchor != null && u2.anchor.length > 0) {
      u1.anchor = u2.anchor
    }

    return u1.toString()
  }

  Url.encoded = urlencoded
  Url.combine = function (url1, url2) {
    if (arguments.length < 2) {
      return arguments[0]
    }
    let _base = url1
    for (let i = 1; i < arguments.length; i++) {
      _base = combine(_base, arguments[i]).toString()
    }
    return _base
  }
  Url.parseSearch = parseSearch
  window[name] = Url
  if (typeof window.define === 'function') {
    window.define(name, [], function () { return Url })
  }
})(window, 'Url')
